-
Notifications
You must be signed in to change notification settings - Fork 29
Add maxflow #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add maxflow #24
Conversation
I will add tests and an example code that is submittable to D - Maxflow, but I will open the PR now. Waiting everyone for review! |
if res == up { | ||
break; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | |
} | |
self.iter[v] = self.graph.g[v].len(); |
This is an important bit of the algorithm that we should not omit. Alternatively we can do
self.level[v] = self.graph.g.len();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right. It is difficult to translate C++ for
-statements with side effects...
src/maxflow.rs
Outdated
pub fn add_edge(&mut self, from: usize, to: usize, cap: Cap) -> usize { | ||
assert!(from < self._n); | ||
assert!(to < self._n); | ||
assert!(Cap::zero() < cap); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert!(Cap::zero() < cap); | |
assert!(Cap::zero() <= cap); |
src/maxflow.rs
Outdated
pub fn add_edge(&mut self, from: usize, to: usize, cap: Cap) -> usize { | ||
assert!(from < self._n); | ||
assert!(to < self._n); | ||
assert!(Cap::zero() < cap); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert!(Cap::zero() < cap); | |
assert!(Cap::zero() < cap); | |
assert_ne!(from, to); |
or fix rev
calculation please.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you teach me why do we need this? The original documentation says that we can input self-loops to graphs, and I don't think that the code does not work for the situation. What change do I have to apply to rev
calculation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The original implementation has the same bug-ish behavior although both this and the original impl technically "work" as these edges won't be touched... I believe. Though it is better to fix, since this bug brakes a fundamental invariant of this graph structure.
The problem is that; when from == to
, rev
calculated in L:68 will be the index of the edge added in L:69
, i.e. itself instead of the reverse edge.
Since rev
should refer the index of the reversal auxiliary edge, it has to be calculated by
let rev = self.g[to].len() + if from == to { 1 } else { 0 };
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I've understood. Fixing.
src/maxflow.rs
Outdated
fn change_edge(&mut self, i: usize, new_cap: Cap, new_flow: Cap) { | ||
let m = self.pos.len(); | ||
assert!(i < m); | ||
assert!(Cap::zero() < new_flow && new_flow <= new_cap); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert!(Cap::zero() < new_flow && new_flow <= new_cap); | |
assert!(Cap::zero() <= new_flow && new_flow <= new_cap); |
pub fn flow(&mut self, s: usize, t: usize) -> Cap { | ||
self.flow_with_capacity(s, t, Cap::max_value()) | ||
} | ||
pub fn flow_with_capacity(&mut self, s: usize, t: usize, flow_limit: Cap) -> Cap { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the reason of this naming? It make sense to name this a capacity
in some specific context, but here probably doesn't. Is there any reference that this is called capacity?
Maybe flow_with_limit
, flow_with_value_limit
or something like that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have an image of adding new edge from virtual vertex s'
to vertex s
with capacity flow_limit
. But I agree that flow_with_limit
may be more intuitive. I want to ask others' opinion for this point.
pub fn flow_with_capacity(&mut self, s: usize, t: usize, flow_limit: Cap) -> Cap { | ||
let n_ = self._n; | ||
assert!(s < n_); | ||
assert!(t < n_); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert!(t < n_); | |
assert!(t < n_); | |
assert_ne!(s, t); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I should not add this assertion, because the original documentation does not say it disallows the same vertices, and by definition we can return a valid value flow_limit
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By definition in the document appendix.html, we should return 0 I believe, which is very confusing as you might have noticed.
g(v, f) = Sum{...} - Sum{...}
This implies the sum of g(v, f) over all vertices is zero.
g(v, f) = g(v, f') holds for all vertices v other than s and t.
Since s = t, this also implies g(t, f) = g(t, f'). (∵ everywhere except t is fixed, and the sum is fixed)
g(t,f′)−g(t,f) is maximized under these conditions. It returns this g(t,f′)−g(t,f)
Since g(t, f) = g(t, f'), the returned value should be 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I finally understood. It seems to be true that it should return 0. Doesn't the current code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried the original library with a simple example and it returned MAX_VALUE
instead...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't return 0 if the limit wasn't 0, since s
is reachable from itself, and v == self.s
holds in the first call of dfs
.
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test() {
let mut g = MfGraph::new(3);
assert_eq!(0, g.flow_with_capacity(0, 0, 10));
}
}
fails with
Left: 0
Right: 10
If you formulate the max flow problem in LP, the primal would be UNBOUND and the dual would be INFEASIBLE. So it also makes sense to return the infinity.
Since there are multiple way of interpreting the meaning of max flow for s == t
case, I'd just add assert_ne
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reported in atcoder/ac-library#5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACL fixed it :)
src/maxflow.rs
Outdated
fn max_value() -> Self; | ||
} | ||
|
||
impl MfCapacity for i32 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe use macro for these impls?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a preliminary implementation. The internal_type_traits
implementation (#19) provides with more wide variety of features, so I will replace with it once it is merged.
src/internal_queue.rs
Outdated
self.pos = 0; | ||
} | ||
|
||
pub(crate) fn pop(&mut self) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe fn pop(&mut self) -> Option<&T>
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is an enhancement that is not in the original library, but it seems useful and safe, so I'll add it. The discussion about internal_queue
is underway in #13, by the way.
Thanks @MiSawa for your invaluable comments!
pub fn flow_with_capacity(&mut self, s: usize, t: usize, flow_limit: Cap) -> Cap { | ||
let n_ = self._n; | ||
assert!(s < n_); | ||
assert!(t < n_); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't return 0 if the limit wasn't 0, since s
is reachable from itself, and v == self.s
holds in the first call of dfs
.
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test() {
let mut g = MfGraph::new(3);
assert_eq!(0, g.flow_with_capacity(0, 0, 10));
}
}
fails with
Left: 0
Right: 10
If you formulate the max flow problem in LP, the primal would be UNBOUND and the dual would be INFEASIBLE. So it also makes sense to return the infinity.
Since there are multiple way of interpreting the meaning of max flow for s == t
case, I'd just add assert_ne
.
pub fn flow_with_capacity(&mut self, s: usize, t: usize, flow_limit: Cap) -> Cap { | ||
let n_ = self._n; | ||
assert!(s < n_); | ||
assert!(t < n_); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Plus
assert!(t < n_); | |
assert!(t < n_); | |
assert!(Cap::zero() <= flow_limit); |
maybe?
pub fn flow_with_capacity(&mut self, s: usize, t: usize, flow_limit: Cap) -> Cap { | ||
let n_ = self._n; | ||
assert!(s < n_); | ||
assert!(t < n_); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reported in atcoder/ac-library#5
@MiSawa Sorry to be late. I think I added all the assertions you mentioned and fixed all the mistakes. Now I'm going to add some tests. Do you have any good idea of test graphs? |
Thank you for being patient about all my nitpicks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM except for missing tests :)
|
||
impl<Cap> MfGraph<Cap> | ||
where | ||
Cap: Integral, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually the algorithm doesn't rely on the integrality.... Though it does rely on Ord
. Not sure what we should do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One possible way is to define a trait dedicated for max-flow capacities, which is my previous implementation. Refer to this commit to see the differences. However this is a bit like a repetition of Integral
trait in internal_type_traits
module, that's why I simply reused it.
At least the original library guarantees only int
and ll
, I guess this is already sufficient.
By the way, can this implementation be applied to floating-point capacity? I mean, don't we need some additional treatment of precision errors?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question!
I believe so long as min(x, y) in {x, y} and x-x = 0, the algorithm work fine-ish. e.g. for floating point numbers without NaN / inf, polynomial, etc.
I said fine-ish since although the time complexity doesn't get worse, there are errors that are unavoidable without using multi-precision floating point number. Guaranteeing the accuracy of the returned flow value and flows on edges would be hard. Farthemore since cut is discrete, the min-cut verticis can be different from the correct one although the cut value should be similar.
In competitive programming, I think I have seen some problems that uses polynomial flows (or big-M instead) and floating point numbers (came from binary/ternary search, can be transformed to a search on rational numbers though require a lot more coding).
Though maybe it will be very rare in AtCoder at least, since ACL doesn't support them.
About the tests:
(I'm not a lawyer, I'm not confident at all.. After all, do I even have to consider the licenses about those small graphs?) |
I think CC-BY-SA 3.0 applies to the picture itself, not to the graph instance that is represented by the picture. Though not really sure, and it's safer to mention the origin anyway.
NYSL! Added a comment in the gist :) |
|
|
||
#[test] | ||
#[allow(clippy::many_single_char_names)] | ||
fn test_max_flow_misawa() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might have done already, but verified locally that it gonna take very long with n=20 and take forever with n=100 when we deleted L:189 and L:209 as intended :)
@qryxip Maybe we can merge this already. |
No description provided.